package com.googlecode.cswish.struts.hibernate.ehcache;

import java.io.Serializable;
import java.util.Iterator;

import net.sf.ehcache.Element;
import net.sf.ehcache.search.attribute.AttributeExtractor;
import net.sf.ehcache.search.attribute.AttributeExtractorException;

import org.hibernate.cache.spi.CacheKey;
import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.hibernate.persister.entity.EntityPersister;

import com.googlecode.cswish.struts.hibernate.HibernateConfiguration;
import com.googlecode.cswish.struts.spring.ObjectFactory;

public class HibernateNameAttributeExtractor implements AttributeExtractor {
	
	private final static int PROPERTY_CLASSNAME = -2;
	private final static int PROPERTY_ID = -3;
	private final static int PROPERTY_UNKNOWN = -1;
	
	private int propertyIndex = PROPERTY_UNKNOWN;
	private int embedIndex = PROPERTY_UNKNOWN;

	public Object attributeFor(Element element) {
		CacheEntry cacheEntry = (CacheEntry) element.getValue();
		return cacheEntry.getDisassembledState();
	}

	@Override
	public Object attributeFor(Element element, String property) throws AttributeExtractorException {
		if (propertyIndex == PROPERTY_UNKNOWN) {
			if ("class".equals(property)) {
				propertyIndex = PROPERTY_CLASSNAME;
			} else if ("id".equals(property)){
				propertyIndex = PROPERTY_ID;
			} else {
				CacheEntry cacheEntry = (CacheEntry) element.getValue();
				String entityName = cacheEntry.getSubclass();
				int index = property.indexOf('.');
				if (index > 0) {
					String subProperty = property.substring(index + 1);
					property = property.substring(0, index);
					
					propertyIndex = getPropertyIndex(entityName, property);
					
					HibernateConfiguration config = ObjectFactory.getInstance().getBean(HibernateConfiguration.class, false);
					PersistentClass persistentClass = config.getClassMapping(entityName);
					Iterator iterator = persistentClass.getPropertyClosureIterator();
					while (iterator.hasNext()) {
						Property prop = (Property) iterator.next();
						if (property.equalsIgnoreCase(prop.getName())) {
							// found it, continue check the position of subProperty
							Value value = prop.getValue();
							Component component = (Component)value;
							Iterator<Property> propIter = component.getPropertyIterator();
							int subIndex = 0;
							while (propIter.hasNext()) {
								Property subProp = propIter.next();
								if (subProp.getName().equals(subProperty)) {
									embedIndex = subIndex;
									break;
								}
								subIndex++;
							}
							break;
						}
					}
				} else {
					propertyIndex = getPropertyIndex(entityName, property);
				}
			}
		}
		
		if (propertyIndex == PROPERTY_CLASSNAME) {
			CacheEntry cacheEntry = (CacheEntry) element.getValue();
			return cacheEntry.getSubclass();
		} else if (propertyIndex == PROPERTY_ID) {
			CacheKey cacheKey = (CacheKey)element.getKey();
			return cacheKey.getKey();
		} else if (propertyIndex >= 0) {
			CacheEntry cacheEntry = (CacheEntry) element.getValue();
			Serializable[] values = cacheEntry.getDisassembledState();
			if (propertyIndex < values.length) {
				if (embedIndex == PROPERTY_UNKNOWN) {
					return values[propertyIndex];
				} else {
					return ((Object[])values[propertyIndex])[embedIndex];
				}
			}
		}
		return null;
	}
	
	private int getPropertyIndex(String entityName, String property) {
		HibernateConfiguration config = ObjectFactory.getInstance().getBean(HibernateConfiguration.class, false);
		SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor)config.getSessionFactory();
		EntityPersister entityPersister = sessionFactory.getEntityPersister(entityName);
		String[] names = entityPersister.getPropertyNames();
		
		int propertyIndex = names.length;			// set the value out of the arrray length to avoid search again
		for (int i = 0; i < names.length; i++) {
			if (names[i].equals(property)) {
				propertyIndex = i;
				break;
			}
		}
		return propertyIndex;
	}
}